package com.tang.sharding.core;

import com.tang.sharding.ShardProperties;
import com.tang.sharding.enums.ShardingCycle;
import io.shardingsphere.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
import io.shardingsphere.api.algorithm.sharding.standard.RangeShardingAlgorithm;
import io.shardingsphere.api.config.rule.TableRuleConfiguration;
import io.shardingsphere.api.config.strategy.ShardingStrategyConfiguration;
import io.shardingsphere.api.config.strategy.StandardShardingStrategyConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

/**
 * @author Tang
 * @classname ShardTableRuleConfig
 * @description [ 规则构建类 ]
 * @date 2021/7/31 11:34
 */
@Slf4j
public class ShardTableRuleConfig {

    private final ShardProperties.TableRule tableRule;

    private final Function<String, Object> function = (item -> {
        try {
            return Class.forName(item).newInstance();
        } catch (Exception e) {
            log.error("实现类{}不存在!", item);
            e.printStackTrace();
        }
        return null;
    });

    public ShardTableRuleConfig(ShardProperties.TableRule tableRule) {
        this.tableRule = tableRule;
    }

    /**
     * 构建规则
     *
     * @param dataSourceName
     * @return
     */
    public TableRuleConfiguration buildTableRule(String dataSourceName) {
        String logicTable = tableRule.getLogicTable();
        String shardColumn = tableRule.getShardColumn();
        ShardingCycle shardingCycle = tableRule.getShardingCycle();
        ShardProperties.ShardingAlgorithm shardingAlgorithm = tableRule.getShardingAlgorithm();
        // 自定义部分
        ShardProperties.CustomCycle customCycle = tableRule.getCustomCycle();
        // 规则设置
        TableRuleConfiguration tableRule = new TableRuleConfiguration();
        tableRule.setLogicTable(logicTable);
        tableRule.setActualDataNodes(
                shardingCycle == ShardingCycle.NON ? customCycle.getActualDataNodes() :
                        shardingCycle.getActualDataNodes(dataSourceName, logicTable)
        );
        String preciseAlgorithmClassName = shardingAlgorithm.getPreciseAlgorithmClassName();
        String rangeAlgorithmClassName = shardingAlgorithm.getRangeAlgorithmClassName();
        // precise Algorithm
        PreciseShardingAlgorithm preciseShardingAlgorithm = null;
        Optional<PreciseShardingAlgorithm> op1 = Optional.ofNullable(preciseAlgorithmClassName).map(function)
                .filter(item -> item instanceof PreciseShardingAlgorithm)
                .map(item -> (PreciseShardingAlgorithm) item);
        if (!op1.isPresent()) {
            throw new RuntimeException("必须指定精确分片实现类!");
        }
        preciseShardingAlgorithm = op1.get();
        // range Algorithm
        RangeShardingAlgorithm rangeShardingAlgorithm = null;
        Optional<RangeShardingAlgorithm> op2 = Optional.ofNullable(rangeAlgorithmClassName).map(function)
                .filter(item -> item instanceof RangeShardingAlgorithm)
                .map(item -> (RangeShardingAlgorithm) item);
        if (op2.isPresent()) {
            rangeShardingAlgorithm = op2.get();
        }
        // build Rule
        ShardingStrategyConfiguration shardingStrategyConfiguration = new StandardShardingStrategyConfiguration(
                shardColumn, preciseShardingAlgorithm, rangeShardingAlgorithm);
        tableRule.setTableShardingStrategyConfig(shardingStrategyConfiguration);
        return tableRule;
    }

    /**
     * 虚拟节点
     *
     * @param shardingEndYear
     * @return
     */
    public static String getActualDataNodes(String shardingEndYear) {
        int maxShardingEndYear = Integer.parseInt(shardingEndYear);
        Set<String> dateSet = new LinkedHashSet<>(8);
        LocalDate startOne = LocalDate.now().withMonth(1).withDayOfMonth(1);
        DayOfWeek dayOfWeek1 = startOne.getDayOfWeek();
        LocalDate s1 = startOne.plusDays(-dayOfWeek1.getValue() + 1);
        LocalDate endOne = LocalDate.now().withYear(maxShardingEndYear).withMonth(1).withDayOfMonth(1);
        DayOfWeek dayOfWeek2 = endOne.getDayOfWeek();
        LocalDate s2 = endOne.plusDays(-dayOfWeek2.getValue() + 1);
        while (s1.isBefore(s2) || (s1.compareTo(s2) == 0)) {
            s1 = s1.plusWeeks(1);
            dateSet.add(String.format("'%s'", s1));
        }
        return StringUtils.join(dateSet, ",");
    }

}